package com.effectiv.gooruda.rest.api.service;

import java.io.IOException;

import java.util.List;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import com.effectiv.gooruda.rest.api.domain.FileStorage;

import com.effectiv.gooruda.rest.api.domain.User;
import com.effectiv.gooruda.rest.api.operation.FileStorageOperation;
import com.effectiv.gooruda.security.rest.RESTSecurityUtil;
import com.mongodb.BasicDBObject;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSFile;

@Component
@Slf4j
public class FileStorageServiceImpl implements FileStorageOperation {

	@Autowired
	private MongoTemplate mongoTemplate;

	@Autowired
	private GridFsTemplate gridFsTemplate;

	private final String APP_COLLECTION_BASE = "t_app_storage_";

	// TODO - Expose this via the service api/some kind of dao??
	private User findByUserName(String userName) {
		Query query = new Query();
		query.addCriteria(Criteria.where("userName").is(userName));

		User u = mongoTemplate.findOne(query, User.class, "t_app_user_"
				+ RESTSecurityUtil.getAppId());

		return u;
	}

	@Override
	public FileStorage store(String userName, String folder,
			String description, String tags, MultipartFile file) {

		User owner = null;

		if (StringUtil.isNotBlank(userName)) {
			owner = findByUserName(userName);
		}

		String appFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ folder + "." + file.getOriginalFilename();
		FileStorage storage = null;

		Query query = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName));

		storage = mongoTemplate.findOne(query, FileStorage.class,
				APP_COLLECTION_BASE + RESTSecurityUtil.getAppId());

		if (storage != null) {
			throw new IllegalArgumentException("A file with name - "
					+ file.getOriginalFilename()
					+ ", already exists in folder - " + folder
					+ ". You may consider using update operation instead.");
		}

		storage = BeanUtils.instantiate(FileStorage.class);
		storage.setDescription(description);
		storage.setFolder(folder);
		storage.setName(file.getOriginalFilename());
		storage.setOwner(owner);
		storage.setVersion(1);
		storage.setAppFileName(appFileName);
		storage.setTags(StringUtil.split(tags, ","));

		try {

			BasicDBObject metadata = new BasicDBObject();
			metadata.put("version", 1);

			GridFSFile gridFile = gridFsTemplate.store(file.getInputStream(),
					appFileName, file.getContentType(), metadata);
			storage.setGridFileId(gridFile.getId().toString());
			mongoTemplate.save(storage,
					APP_COLLECTION_BASE + RESTSecurityUtil.getAppId());
		} catch (IOException e) {
			// TODO - throw exception to be handled by rest controller
			log.error("Error saving file to grid storage", e);
		}

		return storage;
	}

	// TODO - REVIEW and minimize meta data - only needed is version
	@Override
	public FileStorage update(String fileName, String folder,
			String description, MultipartFile file) {
		String appFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ folder + "." + fileName;

		Query query = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName));

		FileStorage storage = mongoTemplate.findOne(query, FileStorage.class,
				APP_COLLECTION_BASE + RESTSecurityUtil.getAppId());

		if (storage == null) {
			throw new IllegalArgumentException("A file with name - " + fileName
					+ ", does not exist in folder - " + folder
					+ ". Hence unable to update.");
		}

		storage.setDescription(description);

		try {
			BasicDBObject metadata = new BasicDBObject();
			metadata.put("version", storage.getVersion() + 1);

			storage.setVersion(storage.getVersion() + 1);
			GridFSFile gridFile = gridFsTemplate.store(file.getInputStream(),
					appFileName, file.getContentType(), storage);

			storage.setGridFileId(gridFile.getId().toString());
			mongoTemplate.save(storage,
					APP_COLLECTION_BASE + RESTSecurityUtil.getAppId());

		} catch (IOException e) {
			// TODO - throw exception to be handled by rest controller
			log.error("Error saving file to grid storage", e);
		}

		log.info("storage = {}", storage);

		return storage;
	}

	@Override
	public boolean addTags(String fileName, String folder, String tags) {

		String collectionName = APP_COLLECTION_BASE
				+ RESTSecurityUtil.getAppId();

		if (StringUtil.isBlank(tags)) {
			throw new IllegalArgumentException("No tags to add.");
		}

		String appFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ folder + "." + fileName;

		Query query = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName));

		Update update = new Update();
		FileStorage storage = null;

		String t[] = StringUtil.split(tags, ",");

		update.pushAll("tags", t);
		storage = mongoTemplate.findAndModify(query, update, FileStorage.class,
				collectionName);

		return (storage != null);
	}

	@Override
	public boolean removeTags(String fileName, String folder, String tags) {

		String collectionName = APP_COLLECTION_BASE
				+ RESTSecurityUtil.getAppId();

		if (StringUtil.isBlank(tags)) {
			throw new IllegalArgumentException("No tags to add.");
		}

		String appFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ folder + "." + fileName;

		Query query = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName));

		Update update = new Update();
		FileStorage storage = null;

		String t[] = StringUtil.split(tags, ",");

		update.pullAll("tags", t);
		storage = mongoTemplate.findAndModify(query, update, FileStorage.class,
				collectionName);

		return (storage != null);
	}

	@Override
	public boolean removeAllTags(String fileName, String folder) {

		String collectionName = APP_COLLECTION_BASE
				+ RESTSecurityUtil.getAppId();

		String appFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ folder + "." + fileName;

		Query query = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName));

		Update update = new Update();
		FileStorage storage = null;

		update.unset("tags");
		storage = mongoTemplate.findAndModify(query, update, FileStorage.class,
				collectionName);

		return (storage != null);
	}

	@Override
	public boolean removeOwner(String fileName, String folder) {
		String collectionName = APP_COLLECTION_BASE
				+ RESTSecurityUtil.getAppId();

		String appFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ folder + "." + fileName;

		Query query = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName));

		Update update = new Update();
		FileStorage storage = null;

		update.unset("owner");
		storage = mongoTemplate.findAndModify(query, update, FileStorage.class,
				collectionName);

		return (storage != null);
	}

	@Override
	public boolean changeOwner(String fileName, String folder, String oldOwner,
			String newOwner) {
		User owner = null;

		if (StringUtil.isNotBlank(newOwner)) {
			owner = findByUserName(newOwner);
		} else {
			throw new IllegalArgumentException("New user name cannot be blank.");
		}

		String collectionName = APP_COLLECTION_BASE
				+ RESTSecurityUtil.getAppId();

		String appFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ folder + "." + fileName;

		Query query = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName));

		Update update = new Update();
		FileStorage storage = null;

		update.set("owner", owner);
		storage = mongoTemplate.findAndModify(query, update, FileStorage.class,
				collectionName);

		return (storage != null);

	}

	public void findByVersion(String fileName, String folder, int version) {

	}

	@Override
	public FileStorage moveFolder(String fileName, String oldFolder,
			String newFolder) {
		// locate file
		String appFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ oldFolder + "." + fileName;

		Query query = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName));

		FileStorage storage = mongoTemplate.findOne(query, FileStorage.class,
				APP_COLLECTION_BASE + RESTSecurityUtil.getAppId());

		if (storage == null) {
			throw new IllegalArgumentException("A file with name - " + fileName
					+ ", already exists in folder - " + oldFolder);
		}
		// get the last version
		Query queryRawFile = new Query();
		query.addCriteria(Criteria.where("appFileName").is(appFileName)
				.and("metadata.version").is(storage.getVersion()));

		GridFSDBFile file = gridFsTemplate.findOne(queryRawFile);

		// now save new file

		storage.setFolder(newFolder);
		storage.setVersion(1);
		String newAppFileName = RESTSecurityUtil.getApplication().getId() + "."
				+ newFolder + "." + storage.getName();
		storage.setAppFileName(newAppFileName);

		BasicDBObject metadata = new BasicDBObject();
		metadata.put("version", 1);

		GridFSFile gridFile = gridFsTemplate.store(file.getInputStream(),
				newAppFileName, storage.getType(), metadata);
		storage.setGridFileId(gridFile.getId().toString());
		mongoTemplate.save(storage,
				APP_COLLECTION_BASE + RESTSecurityUtil.getAppId());

		// locate all versions and delete them
		gridFsTemplate.delete(query);

		return storage;
	}

	@Override
	public boolean deleteFolder(String folder) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean renameFolder(String oldFolder, String newFolder) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public String deleteByUser(String userName) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String deleteByName(String fileName) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String deleteAllByUser(String userName) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String deleteAll() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public long countByType(String type) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public long countByUser(String userName) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public long count() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public List<FileStorage> findByType(String type, Pageable page) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<FileStorage> findByOwner(String userName, Pageable page) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<FileStorage> findAll(Pageable page) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<FileStorage> findByTags(String[] tags, Pageable page) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public byte[] download(String fileName) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public FileStorage findByName(String fileName, Pageable page) {
		// TODO Auto-generated method stub
		return null;
	}

}
